/*
 * To change this license header, choose License Headers in Project Properties.
 * To change this template file, choose Tools | Templates
 * and open the template in the editor.
 */
package LDVH.ihm;

import LDVH.factories.LivreFactory;
import LDVH.itf.ILivre;
import LDVH.itf.ISection;
import edu.uci.ics.jung.algorithms.layout.KKLayout;
import edu.uci.ics.jung.algorithms.layout.Layout;
import edu.uci.ics.jung.graph.DirectedOrderedSparseMultigraph;
import edu.uci.ics.jung.graph.Graph;
import edu.uci.ics.jung.visualization.VisualizationViewer;
import edu.uci.ics.jung.visualization.control.DefaultModalGraphMouse;
import edu.uci.ics.jung.visualization.control.ModalGraphMouse;
import edu.uci.ics.jung.visualization.decorators.EdgeShape;
import edu.uci.ics.jung.visualization.decorators.ToStringLabeller;
import edu.uci.ics.jung.visualization.renderers.BasicVertexLabelRenderer;
import edu.uci.ics.jung.visualization.renderers.Renderer;
import edu.uci.ics.jung.visualization.renderers.VertexLabelAsShapeRenderer;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.Paint;
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
import javax.swing.JPanel;
import org.apache.commons.collections15.Transformer;

/**
 *
 * @author daniele
 */
public class LdvhGraphViewer {
    private final VisualizationViewer<Section, Enchainement> server;
    private final Layout<Section, Enchainement> layout;
    private final Graph<Section, Enchainement> graph;
    private final Color emptyBgColor;
    private final static Color bgColor = Color.WHITE;
    
    private final Transformer<Section, Paint> vertexPainter =
        new Transformer<Section, Paint>(){
            @Override
            public Paint transform(Section section) {
                switch (section.getStatus()) {
                    case Section.INITIAL:
                        return Color.GREEN;
                    case Section.OK:
                        return Color.LIGHT_GRAY;
                    case Section.UNREACHABLE:
                        return Color.RED;
                    case Section.FINAL:
                        return Color.YELLOW;
                }
                return Color.BLACK;
            }
        };

    private final Transformer<Enchainement, Paint> edgePainter =
            new Transformer<Enchainement, Paint>() {

        @Override
        public Paint transform(Enchainement e) {
            switch(e.status) {
                case Enchainement.COND:
                    return Color.RED;
                case Enchainement.OK:
                    return Color.BLACK;
            }
            return Color.MAGENTA;
        }
    };
    
    public LdvhGraphViewer(Dimension dimension) {
        graph = new DirectedOrderedSparseMultigraph<>();
        layout = new KKLayout<>(graph);
        layout.setSize(dimension);
        server = new VisualizationViewer<>(layout);
        emptyBgColor = ((JPanel) server).getBackground();
        setUp();
    }
    
    private void setUp() {
        BasicVertexLabelRenderer<Section, Enchainement> vertexLabelRenderer = 
                new BasicVertexLabelRenderer<>();
        vertexLabelRenderer.setPosition( Renderer.VertexLabel.Position.CNTR);
        server.getRenderContext().setVertexFillPaintTransformer(vertexPainter);
        server.getRenderContext().setEdgeDrawPaintTransformer(edgePainter);
        server.getRenderContext().setArrowDrawPaintTransformer(edgePainter);
        server.getRenderContext().setArrowFillPaintTransformer(edgePainter);
        
        VertexLabelAsShapeRenderer<Section, Enchainement> vlasr = 
                new VertexLabelAsShapeRenderer<>(server.getRenderContext());
 
        server.getRenderContext().setVertexShapeTransformer(vlasr);
        server.getRenderer().setVertexLabelRenderer(vertexLabelRenderer);
        
        server.getRenderContext().setVertexLabelTransformer(new ToStringLabeller());
        server.getRenderContext().setEdgeLabelTransformer(new ToStringLabeller());
        server.getRenderContext().setEdgeShapeTransformer(new EdgeShape.Line());

        final DefaultModalGraphMouse graphMouse = new DefaultModalGraphMouse();
        graphMouse.setMode(ModalGraphMouse.Mode.PICKING);
        server.setGraphMouse(graphMouse);
    }
    
    public JPanel getPanel() {
        return (JPanel) server;
    }
    
    public void init() {
        ((JPanel) server).setBackground(bgColor);
        ILivre livre = LivreFactory.getLivre();
        for (ISection section : livre.listerSections()) {
            Section s = new Section(section);
            this.graph.addVertex(s);
        }
        
        for (ISection depart : livre.listerSections()) {
            Section dep = new Section(depart);
            for (ISection arrivee : depart.listerEnchainements()) {
                Section arr = new Section(arrivee);
                this.graph.addEdge(new Enchainement(dep, arr), dep, arr);
            }               
        }
        update();
    }
    
    public void reset() {
        Section vertices[] = graph.getVertices().toArray(new Section[0]);
        for (Section vertex : vertices) {
            graph.removeVertex(vertex);
        }
        
        ((JPanel)server).setBackground(emptyBgColor);
        update();
    }
    
    public void addSection(ISection section) {
        Section s = new Section(section);            
        graph.addVertex(s);
        update();
    }
    
    public void addEnchainement(ISection sections[]) {
        Section dep = new Section(sections[0]);
        Section arr = new Section(sections[1]);
        Enchainement e = new Enchainement(dep, arr);
        graph.addEdge(e, dep, arr);
        update();
    }
    
    public void addEnchainement(ISection s1, ISection s2) {
        addEnchainement(new ISection[]{s1, s2});
   }
    
    public void removeSection(ISection section) {
        Section s = new Section(section);
        graph.removeVertex(s);
        update();
   }
    
    public void removeEnchainement(ISection sections[]) {
        Section dep = new Section(sections[0]);
        Section arr = new Section(sections[1]);
        Enchainement e = new Enchainement(dep, arr);
        graph.removeEdge(e);
        update();
    }
    
    public void setUnreachable(List<ISection> sections) {
        for (Section s : graph.getVertices()) {
            s.status = (sections.remove(s.section)) ? Section.UNREACHABLE : Section.OK;
        }
        update();
    }
    
    public void update() {
        server.repaint();
    }

    void setInsatisfiable(List<ISection[]> insatisfiables) {
        List<Enchainement> insats = new ArrayList<>();
        for (ISection sections[] : insatisfiables) {
            Section dep = new Section(sections[0]);
            Section arr = new Section(sections[1]);
            insats.add(new Enchainement(dep, arr));
        }
        
        for (Enchainement e : graph.getEdges()) {
            e.status = (insats.remove(e)) ? Enchainement.COND : Enchainement.OK;
        }
        update();
    }
    
    private class Section {
        private static final int INITIAL = 0;
        private static final int UNREACHABLE = 1;
        private static final int OK = 2;
        private static final int FINAL = 3;
        
        private final ISection section;
        private int status;
        
        private Section(ISection section) {
            this.section = section;
            status = OK;
        }      
        
        private int getStatus() {
            if (status == UNREACHABLE)
                return status;
            
            ISection init = LivreFactory.getLivre().getSectionInitiale();
            
            if (section.equals(init))
                status = INITIAL;
            else
                status = (section.listerEnchainements().length == 0) ? FINAL : OK;
            return status;
        }
                
        @Override
        public boolean equals(Object o) {
            if ( ! (o instanceof Section))
                return false;
            Section s = (Section) o;
            
            return s.section.equals(section);
        }

        @Override
        public int hashCode() {
            int hash = 7;
            hash = 61 * hash + Objects.hashCode(this.section);
            return hash;
        }
        
        @Override 
        public String toString() {
            return section.toString();
        }
    }
    
    private class Enchainement {
        private static final int COND = 1;
        private static final int OK = 0;
        
        private final Section depart, arrivee;
        private int status;
        
        private Enchainement(Section depart, Section arrivee) {
            this.depart = depart;
            this.arrivee = arrivee;
            status = OK;
        }        
        
        @Override
        public boolean equals(Object o) {
            if ( ! (o instanceof Enchainement))
                return false;
            Enchainement e = (Enchainement) o;
            
            return e.depart.equals(depart) && e.arrivee.equals(arrivee);
        }

        @Override
        public int hashCode() {
            int hash = 7;
            hash = 59 * hash + Objects.hashCode(this.depart);
            hash = 59 * hash + Objects.hashCode(this.arrivee);
            return hash;
        }
        
        @Override 
        public String toString() {
                Integer condsIds[] = depart.section.getConditions(arrivee.section);
                StringBuilder sb = new StringBuilder("<html>");
                
                int i = 1;
                for (int condId : condsIds) {
                    if (i == 2) {
                        sb.append("...");
                        break;
                    }
                    sb.append(LivreFactory.getLivre().getGestObjet().getDescription(condId));
                    sb.append("<br>");
                    i++;
                }

                return sb.toString();
        }
    }
}
